/*
 * Copyright © 2009 Gerald Isaac Schwarz
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package base;

import java.awt.Point;
import java.util.ArrayList;
import java.awt.Dimension;

public abstract class Board {
	
	private Player currentPlayer;

	/**
	 * Get the player who's turn it is to make a move.
	 * @return The player who's turn it is to make a move.
	 */
	public Player getCurrentPlayer() {
		return this.currentPlayer;
	}
	
	/**
	 * Set the player who's turn it is to make a move.
	 * @param player The player who's turn it is to make a move.
	 */
	void setCurrentPlayer(Player player) {
		this.currentPlayer = player;
	}
	
	private boolean hasPlayer(Player player, ArrayList<Player> players) {
		for (Player player2 : players) {
			if (player2 == player) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Get all the players who are participating in the game.
	 * @return An ArrayList of the players who are participating in the game.
	 */
	public ArrayList<Player> getPlayers() {
		ArrayList<Player> players = new ArrayList<Player>();
		
		for (Piece piece : this.getPieces()) {
			Player player = this.getCurrentState(piece).getOwner();
			
			if (!this.hasPlayer(player, players)) {
				players.add(player);
			}
		}
		
		return players;
	}
	
	public Player getPlayerById(int id) {
		for (Player player : this.getPlayers()) {
			if (player.getId() == id) {
				return player;
			}
		}
		
		return null;
	}
	
	public boolean isCurrentPlayerLast() {
		for (Player player : this.getPlayers()) {
			if (player.getId() > this.getCurrentPlayer().getId()) {
				return false;
			}
		}
		
		return true;
	}
	
	public Player getLastPlayer() {
		Player last = this.getPlayers().get(0);
		
		for (Player player : this.getPlayers()) {
			if (player.getId() > last.getId()) {
				last = player;
			}
		}
		
		return last;
	}
	
	/**
	 * Advance to the next player.
	 * @return The player who is now the current player.
	 */
	public Player getNextPlayer() {
		if (this.isCurrentPlayerLast()) {
			int lowestId = this.getPlayers().get(0).getId();
			
			for (Player player : this.getPlayers()) {
				if (player.getId() < lowestId) {
					lowestId = player.getId();
				}
			}
			
			return this.getPlayerById(lowestId);
		}
		else {
			int lowestId = this.getCurrentPlayer().getId();
			int highestId = this.getLastPlayer().getId();
			
			for (Player player : this.getPlayers()) {
				if (lowestId  < player.getId()&& player.getId() < highestId) {
					highestId = player.getId();
				}
			}
			
			return this.getPlayerById(highestId);
		}
	}
	
	/**
	 * Filter the list of players so that it will only include players other than the specified player.
	 * @param player The player we don't want the players to match.
	 * @return An ArrayList of all the players that aren't the specified player.
	 */
	public ArrayList<Player> filterPlayersByPlayer(Player player) {
		ArrayList<Player> filteredPlayers = new ArrayList<Player>();
		
		for (Player p : this.getPlayers()) {
			if (p != player) {
				filteredPlayers.add(p);
			}
		}
		
		return filteredPlayers;
	}
	
	/**
	 * Get all the pieces in the game.
	 * @return An ArrayList of all the pieces in the game.
	 */
	public abstract ArrayList<Piece> getPieces();
	
	/**
	 * Get the piece located at the specified coordinates.
	 * @param location The location of the piece which is to be retrieved.
	 * @return The piece that is located at the specified location. Null if there is no piece there or if it's not alive.
	 */
	public Piece getPiece(Point location) {
		for (Piece piece : this.getPieces()) {
			if (this.getCurrentState(piece).getLocation().equals(location) && this.getCurrentState(piece).isAlive()) {
				return piece;
			}
		}
		
		return null;
	}
	
	public abstract State getCurrentState(Piece piece);
	
	/**
	 * Check whether or not the piece can move into the specified destination.
	 * @param destination The destination the piece wants to reach.
	 * @param player The player who owns the piece.
	 * @param move The move the piece is using to reach the destination.
	 * @return True if the piece is capable of entering the destination, false if it can't.
	 */
	public boolean isAvailable(Point location, Player player, Move move) {
		if (this.contains(location) && 
				((this.isEmpty(location) && move.isMove()) || 
						(this.isOccupiedByEnemy(location, player) && move.isAttack()))) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * Filter a list of pieces so that it will only include pieces that don't belong to the specified player.
	 * @param pieces An ArrayList of the pieces you wish to filter.
	 * @param type The player you want the piece's owner not to match.
	 * @return An ArrayList of all the pieces that belong to the specified player.
	 */
	public ArrayList<Piece> filterPiecesByPlayer(ArrayList<Piece> pieces, Player player) {
		ArrayList<Piece> filteredPieces = new ArrayList<Piece>();
		
		for (Piece piece : pieces) {
			if (this.getCurrentState(piece).getOwner() != player) {
				filteredPieces.add(piece);
			}
		}
		
		return filteredPieces;
	}
	
	/**
	 * Filter a list of pieces so that it will only include pieces that belong to the specified player.
	 * @param pieces An ArrayList of the pieces you wish to filter.
	 * @param type The player you want the piece's owner to match.
	 * @return An ArrayList of all the pieces who belong to the specified player.
	 */
	public ArrayList<Piece> getPiecesByPlayer(ArrayList<Piece> pieces, Player player) {
		ArrayList<Piece> filteredPieces = new ArrayList<Piece>();
		
		for (Piece piece : pieces) {
			if (this.getCurrentState(piece).getOwner() == player) {
				filteredPieces.add(piece);
			}
		}
		
		return filteredPieces;
	}
	/**
	 * Get all pieces who's x location is equal to the specified x coordinate.
	 * @param x The column who's pieces you wish to retrieve.
	 * @return An ArrayList of all the pieces who's x location equals the specified x coordinates.
	 */
	public ArrayList<Piece> getPiecesByX(int x) {
		ArrayList<Piece> pieces = new ArrayList<Piece>();
		
		for (Piece piece : this.getPieces()) {
			if (this.getCurrentState(piece).isAlive() && this.getCurrentState(piece).getLocation().x == x) {
				pieces.add(piece);
			}
		}
		
		return pieces;
	}
	
	/**
	 * Get all pieces who's y location is equal to the specified y coordinate.
	 * @param y The row who's pieces you wish to retrieve.
	 * @return An ArrayList of all the pieces who's y location equals the specified y coordinates.
	 */
	public ArrayList<Piece> getPiecesByY(int y) {
		ArrayList<Piece> pieces = new ArrayList<Piece>();
		
		for (Piece piece : this.getPieces()) {
			if (this.getCurrentState(piece).isAlive() && this.getCurrentState(piece).getLocation().y == y) {
				pieces.add(piece);
			}
		}
		
		return pieces;
	}
	
	/**
	 * Get all the captured pieces held by player.
	 * @param player The player who's pieces are to be retrieved.
	 * @return An ArrayList of all pieces that are currently held captive by player.
	 */
	public ArrayList<Piece> getCapturedPieces(Player player) {
		ArrayList<Piece> capturedPieces = new ArrayList<Piece>();
		
		for (Piece piece : this.getPieces()) {
			if (this.getCurrentState(piece).isAlive() == false && this.getCurrentState(piece).getOwner() == player) {
				capturedPieces.add(piece);
			}
		}
		
		return capturedPieces;
	}
	
	/**
	 * Checks whether or not the specified location has a living piece in it.
	 * @param location The location which is to be examined.
	 * @return True if the specified location is empty, false if there is a living piece there.
	 */
	public boolean isEmpty(Point location) {
		if (this.getPiece(location) == null) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * Check whether or not the specified location is occupied by an enemy of the player.
	 * @param location The location being examined.
	 * @param player The player for whom we are testing.
	 * @return True if it is, false if it's not.
	 */
	public boolean isOccupiedByEnemy(Point location, Player player) {
		Piece occupant = this.getPiece(location);
		
		if (occupant != null && this.getCurrentState(occupant).getOwner() != player) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * Get the height and width of the board.
	 * @return A Dimension which stores the number of rows and columns in the board.
	 */
	public abstract Dimension getSize();
	
	/**
	 * Tells you whether or not the given location is inside the boundaries of the board.
	 * @param location The location which is to be examined.
	 * @return True if the location is within the board, false if it's outside the boundaries.
	 */
	public boolean contains(Point location) {
		Dimension size = this.getSize();
		
		if (location.x >= 0 && location.y >= 0 && location.x < size.width && location.y < size.height) {
			return true;
		}
		else {
			return false;
		}
	}

	public abstract ArrayList<UpgradeZone> getUpgradeZones();

	public ArrayList<UpgradeZone> getUpgradeZonesByPlayer(Player owner) {
		ArrayList<UpgradeZone> filteredUpgradeZones = new ArrayList<UpgradeZone>();
		
		for (UpgradeZone upgradeZone : this.getUpgradeZones()) {
			for (Player player : upgradeZone.getPlayers()) {
				if (player == owner) {
					filteredUpgradeZones.add(upgradeZone);
					break;
				}
			}
		}
		
		return filteredUpgradeZones;
	}
	
	public boolean isInUpgradeZone(Piece piece) {
		ArrayList<UpgradeZone> upgradeZones = this.getUpgradeZonesByPlayer(this.getCurrentState(piece).getOwner());
		
		for (UpgradeZone upgradeZone : upgradeZones) {
			if (upgradeZone.getZone().contains(this.getCurrentState(piece).getLocation())) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Filter a list of pieces so that it will only include pieces that match the specified type.
	 * @param pieces An ArrayList of the pieces you wish to filter.
	 * @param ruleType An instance of the type you want them to match.
	 * @return An ArrayList of all the pieces who match the specified type.
	 */
	public ArrayList<Piece> getPiecesByRuleType(ArrayList<Piece> pieces, String ruleType) {
		ArrayList<Piece> filteredPieces = new ArrayList<Piece>();
		
		for (Piece piece : pieces) {
			if (piece.getRuleType().equals(ruleType)) {
				filteredPieces.add(piece);
			}
		}
		
		return filteredPieces;
	}
	
	/**
	 * Filter a list of pieces so that it will only include pieces that don't match the specified type.
	 * @param pieces An ArrayList of the pieces you wish to filter.
	 * @param ruleType An instance of the type you don't want them to match.
	 * @return An ArrayList of all the pieces who don't match the specified type.
	 */
	public ArrayList<Piece> filterPiecesByRuleType(ArrayList<Piece> pieces, String ruleType) {
		ArrayList<Piece> filteredPieces = new ArrayList<Piece>();
		
		for (Piece piece : pieces) {
			if (!piece.getRuleType().equals(ruleType)) {
				filteredPieces.add(piece);
			}
		}
		
		return filteredPieces;
	}
	
	/**
	 * Filter a list of pieces so that it will only include pieces that match the specified type.
	 * @param pieces An ArrayList of the pieces you wish to filter.
	 * @param type An instance of the type you want them to match.
	 * @return An ArrayList of all the pieces who match the specified type.
	 */
	public ArrayList<Piece> getPiecesByType(ArrayList<Piece> pieces, String type) {
		ArrayList<Piece> filteredPieces = new ArrayList<Piece>();
		
		for (Piece piece : pieces) {
			if (piece.getType().equals(type)) {
				filteredPieces.add(piece);
			}
		}
		
		return filteredPieces;
	}
	
	/**
	 * Filter a list of pieces so that it will only include pieces that don't match the specified type.
	 * @param pieces An ArrayList of the pieces you wish to filter.
	 * @param type An instance of the type you don't want them to match.
	 * @return An ArrayList of all the pieces who don't match the specified type.
	 */
	public ArrayList<Piece> filterPiecesByType(ArrayList<Piece> pieces, String type) {
		ArrayList<Piece> filteredPieces = new ArrayList<Piece>();
		
		for (Piece piece : pieces) {
			if (!piece.getType().equals(type)) {
				filteredPieces.add(piece);
			}
		}
		
		return filteredPieces;
	}
}
